// Copyright Leonid Shlyapnikov 2011
// Distributed under the GNU Lesser General Public License http://www.gnu.org/licenses/lgpl.html
//    author: Leonid Shlyapnikov, created on: Jun 12, 2011

#ifndef FIXPARSER_CPP_SRC_MAIN_FIXPARSER_FIXMESSAGE_H_
#define FIXPARSER_CPP_SRC_MAIN_FIXPARSER_FIXMESSAGE_H_

#include <boost/integer_traits.hpp>
#include <boost/cstdint.hpp>
#include <boost/assert.hpp>
#include <boost/utility.hpp>
#include <boost/mpl/size.hpp>
#include <boost/mpl/index_of.hpp>
#include <boost/foreach.hpp>
#include <boost/exception/all.hpp>
#include <exception>
#include <string>
#include <cstring>
#include <sstream>

#include "./MemoryArea.h"
#include "./Base.h"
#include "./Field.h"
#include "./FieldList.h"
#include "./String.h"
#include "./ChecksumConverter.h"
#include "./Uint32ToStringConverter.h"
#include "./log.h"

namespace fixparser {

template<class MSG_DEF_T>
class FixMessage : public Base {
public:
    friend class MemoryArea;

    bool isFieldIndexed(size_t tagNum) const {
        const size_t index = MSG_DEF_T::tagToIndex(tagNum);
        if (MSG_DEF_T::isInvalidIndex(index)) {
            return false;
        }

        Field* field = this->cachedFields_[index];
        return (0 != field);
    }

    size_t numberOfFields() const {
        return this->fieldList_.size() + 1;
    }

    const String& value(size_t tagNum) const {
        const Field* f = field(tagNum);
        // never returns null;
        BOOST_ASSERT(0 != f);
        return f->value();
    }

    /**
     * Throws std::invalid_argument if TAG_NUM field is not set.
     */
    const Field* field(size_t tagNum) const {
        if (10 == tagNum) {
            return &checksumField_;
        }
        const size_t index = tagToIndexWithCheck(tagNum);
        Field* field = this->cachedFields_[index];
        if (0 == field) {
            std::stringstream error;
            error << "Indexed field: " << tagNum << " is not set. Use isFieldIndexed(size_t) to find if indexed field is set";
            BOOST_THROW_EXCEPTION(std::invalid_argument(error.str()));
        }
        return field;
    }

    // TODO(shl) do you need two of them?
    template<class STR>
    void setField(size_t tagNum, const STR& newValue) {
        Field* f = getFieldFromCacheOrCreate(tagNum);
        BOOST_ASSERT(0 != f);
        f->setValue(newValue);
    }

    // TODO(shl) do you need two of them?
    void setField(size_t tagNum, const char* newValue) {
        Field* f = getFieldFromCacheOrCreate(tagNum);
        BOOST_ASSERT(0 != f);
        f->setValue(newValue);
    }

    void setChecksum() {
        const unsigned int checksum = this->calculateChecksum();
        const std::string checksumStr = ChecksumConverter::instance().checksumToString(checksum);
        this->checksumField_.setValue(checksumStr);
    }

    void setBodyLength() {
        Field* field = getRequiredFieldFromCache(9);
        boost::uint32_t bodyLength = this->calculateBodyLength();
        field->setValue(Uint32ToStringConverter::toString(bodyLength));
    }

    void setAll() {
        // the order is important, checksum counts BodyLength tag
        setBodyLength();
        setChecksum();
    }

    template<class VISITOR>
    void acceptVisitor(VISITOR* visitor) {
        // don't forget about the required order of fields (check the spec):
        // 8=..|9=..|34=..(??)|35=..(??)| ... 10=..|

        BOOST_FOREACH(const Field& field, this->fieldList_) {
            field.acceptVisitor(visitor);
        }

        if (false == checksumField_.isEmpty()) {
            checksumField_.acceptVisitor(visitor);
        }
    }

    template<class VISITOR>
    void acceptVisitor(VISITOR* visitor) const {
        BOOST_FOREACH(const Field& field, this->fieldList_) {
            field.acceptVisitor(visitor);
        }

        if (false == checksumField_.isEmpty()) {
            checksumField_.acceptVisitor(visitor);
        }
    }

    boost::uint32_t calculateChecksum() const {
        boost::uint32_t result = 0;

        BOOST_FOREACH(const Field& field, this->fieldList_) {
            result += field.calculateChecksum();
        }

        return result;
    }

    boost::uint32_t calculateBodyLength() const {
        size_t result = 0;

        // http://en.wikipedia.org/wiki/FIX_protocol#Body_length
        for (const Field* field = this->field(35); field != 0; field = field->next()) {
            result += field->tag().length() + field->value().length() + 2; // tag=value<SOH>
        }

        // this is not really going to happen, but just in case
        if (result > boost::integer_traits<uint32_t>::const_max) {
            std::stringstream error;
            error << "FixMessage is too long. BodyLength does not fit into uint32_t, current length: " << result;
            BOOST_THROW_EXCEPTION(std::length_error(error.str()));
        }

        return static_cast<boost::uint32_t>(result);
    }

private:
    explicit FixMessage(MemoryArea* const ma) : memoryArea_(ma), fieldList_(ma), checksumField_(ma) {
        init();
    }

    virtual ~FixMessage() {}

    void init() {
        // set cachedFields to 0, don't want to use sizeof static array, if decide to switch to dynamic array, this will be broken
        std::memset(this->cachedFields_, 0, MSG_DEF_T::NUMBER_OF_INDEXED_TAGS * sizeof(cachedFields_[0]));
        // 8,9 and 35 should alwasy be created in this order
        getRequiredFieldFromCache(8);
        getRequiredFieldFromCache(9);
        getRequiredFieldFromCache(35);
        // 10 is alwasy the last tag
        this->checksumField_.setTag(Uint32ToStringConverter::toString(10));
    }

    size_t tagToIndexWithCheck(size_t tagNum) const {
        const size_t index = MSG_DEF_T::tagToIndex(tagNum);
        if (MSG_DEF_T::isInvalidIndex(index)) {
            std::stringstream error;
            error << "Field: " << tagNum << " canot be accessed by index (not configured as indexed tag)";
            BOOST_THROW_EXCEPTION(std::invalid_argument(error.str()));
        }
        return index;
    }

    Field* getFieldFromCacheOrCreate(size_t tagNum) {
        if (10 == tagNum) {
            return &checksumField_;
        }

        // convert field tag number into field cache index
        const size_t index = MSG_DEF_T::tagToIndex(tagNum);

        if (MSG_DEF_T::isInvalidIndex(index)) {
            // allocate a new field
            Field* field = memoryArea_->create<Field>();
            // add the field to the list of all fields
            fieldList_.push_back(field);
            // set field tag
            field->setTag(Uint32ToStringConverter::toString(tagNum));
            // just return it, it does not have to be cached/indexed
            return field;
        }

        // get it from the cache
        Field* field = this->cachedFields_[index];

        if (0 == field) {
            // allocate a new field
            field = memoryArea_->create<Field>();
            // cache it
            this->cachedFields_[index] = field;
            // add the field to the list of all fields
            fieldList_.push_back(field);
            // set field tag
            field->setTag(Uint32ToStringConverter::toString(tagNum));
        }

        return field;
    }

    Field* getRequiredFieldFromCache(size_t tagNum) {
        if (10 == tagNum) {
            return &checksumField_;
        }

        // get index for required tag
        const size_t index = tagToIndexWithCheck(tagNum);

        // get it from the cache
        Field* field = this->cachedFields_[index];

        if (0 == field) {
            // allocate a new field
            field = memoryArea_->create<Field>();
            // cache it
            this->cachedFields_[index] = field;
            // add the field to the list of all fields
            fieldList_.push_back(field);
            // set field tag
            field->setTag(Uint32ToStringConverter::toString(tagNum));
        }

        return field;
    }

    MemoryArea* const memoryArea_;

    // list of all fields
    FieldList fieldList_;

    // array of pointers to elements in fieldList_
    Field* cachedFields_[MSG_DEF_T::NUMBER_OF_INDEXED_TAGS];

    // a special field for checksum
    Field checksumField_;
};


}

#endif  // FIXPARSER_CPP_SRC_MAIN_FIXPARSER_FIXMESSAGE_H_
